#include "config.h"
#include "expat.h"

#include <iostream>
#include <fstream>
using namespace std;

const std::string CONFIG_EMPTY_STRING = "";

XMLNode::XMLNode() : parentNode(NULL), tagName(""), textContent("") 
{ }

// deletes the children nodes
XMLNode::~XMLNode() { 
  parentNode = NULL;
  for(NodeIter it = children.begin(); it != children.end(); ++it) {
    if(*it) { delete *it; *it = NULL; }
  }
  children.clear();
}

// here we're careful not to create an id attribute if it didn't exist before
const std::string& XMLNode::id() { 
  if(attr.find("id") == attr.end()) { return CONFIG_EMPTY_STRING; }
  return attr["id"]; 
}

// finds the first element matching the input ID
// returns NULL if no element found
XMLNode* XMLNode::getElementById(const char* elemID) {
  if(attr.find("id") != attr.end()) {
    if(attr["id"] == elemID) { return this; }
  }

  XMLNode* node = NULL;
  for(NodeIter it = children.begin(); it != children.end(); ++it) {
    node = (*it)->getElementById(elemID);
    if(node) break;
  }
  return node;
}

// returns the first element matching the tag name
XMLNode* XMLNode::getElementByTagName(const char* _tagName) {
  if(!_tagName || (CONFIG_EMPTY_STRING == _tagName)) { return NULL; }
  if(tagName == _tagName) { return this; }
  XMLNode* retNode = NULL;
  for(NodeIter it = children.begin(); it != children.end(); ++it) {
    retNode = (*it)->getElementByTagName(_tagName);
    if(retNode) { break; }
  }
  return retNode;
}

// if outArr is not NULL, this function appends all matching elements to outArr 
// returns the number of elements found of that tagname
int XMLNode::getElementsByTagName(const char* _tagName, NodeArray* outArr) {
  if(!_tagName || (CONFIG_EMPTY_STRING == _tagName)) { return 0; }
  int retVal = 0;
  if(tagName == _tagName) { 
    if(outArr) { outArr->push_back(this); }
    ++retVal;
  }
  for(NodeIter it = children.begin(); it != children.end(); ++it) {
    retVal += (*it)->getElementsByTagName(_tagName, outArr);
  }
  return retVal;
}

bool XMLNode::hasChildNodes() const { 
  return !children.empty(); 
}

// when parsing is complete, this is the document root
XMLNode* pCurNode = NULL;

// flag to indicate if the previous node we parsed was a text node
XMLNode* pPrevTextNodeSibling = NULL;

int parseLevel = 0;

// if we're starting to parse a new node, and our previous sibling was a 
// text node, then if that node consists solely of whitespace, we want
// to delete that node, else, we want to add it to the parent
// if this is a new char data node, add it to the parent
void processPrevTextNode() {
  if(pPrevTextNodeSibling) {
    std::string::iterator ch;
    std::string& str = pPrevTextNodeSibling->textContent;
    for(ch = str.begin(); ch != str.end(); ++ch) {
      if(*ch != ' ' && *ch != '\n' && *ch != '\t') {
        break;
      }
    }
    if(ch != str.end()) {
      pCurNode->children.push_back(pPrevTextNodeSibling);
//      int n = parseLevel;
//      while(n-- > 0) { cout << "  "; }
//      cout << "*** TEXT NODE ***" << endl;
    }
    else {
      delete pPrevTextNodeSibling;
    }
  }
  pPrevTextNodeSibling = NULL;
}

// this is called by expat when a new XML node is started
// (we move our pointer down to the new node)
void beginParseElement(void *data, const char *el, const char **attrArray) {
//  cout << "Inside beginParseElement(data,'" << el << "',attrArray)" << endl;

  ++parseLevel;

  processPrevTextNode();

  // save parent node
  XMLNode* pParent = pCurNode; // ? pCurNode->parentNode : NULL);

  // create new XML node
//  cout << "pCurNode was " << (int)pCurNode << " with parent " << (int)pParent;
  pCurNode = new XMLNode();
  pCurNode->tagName = el;

//  int n = parseLevel-1;
//  while(n-- > 0) { cout << "  "; }
//  cout << "<" << pCurNode->tagName << ">" << endl;

  // link new node back to its parent, link parent to its new child
  pCurNode->parentNode = pParent;
  if(pParent) {
    pParent->children.push_back(pCurNode);
  }
//  cout << ", now pCurNode is " << (int)pCurNode << ", tagname=" << pCurNode->tagName << " with parent " << (int)pCurNode->parentNode << endl;

  for(int i = 0; attrArray[i]; i += 2) {
    // store this attribute name-value in the current node's map
    pCurNode->attr[ attrArray[i] ] = attrArray[i + 1];
  }
}

// this is called by expat when a text node is encountered
// we append a text node to the current object's children
void parseCharacterData(void *data, const char *s, int len) {
  // if previous sibling node was a char data, we'll append to it
  XMLNode* pTextNode = (pPrevTextNodeSibling ? pPrevTextNodeSibling : new XMLNode());
  pTextNode->parentNode = pCurNode;

  char* szTemp = new char[len+1];
  strncpy(szTemp, s, len);
  szTemp[len] = '\0';
//  cout << "Inside parseCharacterData(data, '" << szTemp << "', " << len << ")" << endl;
  // not the most efficient means, but it gets the job done for now
  pTextNode->textContent += szTemp;
//  cout << "  char data node now is '" << pTextNode->textContent << "'" << endl;
  delete[] szTemp;

  // update our previous char data node with this node
  pPrevTextNodeSibling = pTextNode;
}

void parseCDataSectionStart(void *userData) {
//  cout << "Found a CDATA start" << endl;
}
void parseCDataSectionEnd(void *userData) {
//  cout << "Found a CDATA end" << endl;
}

void parseComment(void *userData, const char* comment) {
  XMLNode* pTextNode = new XMLNode();
  pTextNode->parentNode = pCurNode;
  pTextNode->textContent = "<!--";
  pTextNode->textContent += comment;
  pTextNode->textContent += "-->";

//  cout << "Inside parseComment(userData, '" << comment << "')" << endl;

  pCurNode->children.push_back(pTextNode);
//  cout << "Parent (" << pCurNode->tagName << ") now has " << pCurNode->children.size() << " nodes" << endl;
}

// this is called by expat when the current XML node is completed
// (we move our pointer back to the parent node)
void endParseElement(void *data, const char *el) {
//  cout << "inside endParseElement(" << el << ")" << endl;
//  cout << "pCurNode was " << (int)pCurNode << endl;
  processPrevTextNode();
  --parseLevel;

//  int n = parseLevel;
//  while(n-- > 0) { cout << "  "; }
//  cout << "</" << pCurNode->tagName << ">" << endl;

//  cout << "this node (a <" << pCurNode->tagName << ">) has:" << endl;
//  if(pCurNode->children.size() == 0) { cout << " zero nodes" << endl; }
//  for(int nodeNum = 0; nodeNum < pCurNode->children.size(); ++nodeNum) {
//    cout << "  Node #" << (nodeNum+1) << " is <" << pCurNode->children[nodeNum]->tagName << "> with contents '" 
//      << pCurNode->children[nodeNum]->textContent << "' and " << pCurNode->children[nodeNum]->children.size() 
//      << " child nodes and " << pCurNode->children[nodeNum]->attr.size() << " attributes" << endl;
//  }
  // end this node and step back to the parent
  if(pCurNode && pCurNode->parentNode) {
    pCurNode = pCurNode->parentNode;
  }
//  cout << "now pCurNode is " << (int)pCurNode << endl;
  pPrevTextNodeSibling = NULL;
}

XMLNode* loadConfig(const char* szConfigFilename) {
//  cout << "Opening config file '" << szConfigFilename << "'" << endl;

  // must set to NULL, because the Game owns the Config object once
  // this function returns and will delete it upon Game destruction
  // (leaving this pointer dangling)
  pCurNode = NULL;

  XML_Parser p = XML_ParserCreate(NULL);
  if (! p) {
    cerr << "Couldn't allocate memory for parser\n" << endl;
    exit(-1);
  }

  // read in the file into a buffer
  char* Buff = NULL;
  size_t size;
  ifstream file(szConfigFilename, ios::in|ios::ate|ios::binary);
  if(file.is_open())
  {
    size = file.tellg();
    Buff = new char[size];
    file.seekg(0, ios::beg);
    file.read(Buff, (std::streamsize)size);
    file.close();
//    cout << "The complete config file content is in memory" << endl;
  }
  else {
    cerr << "Unable to open config file" << endl;
    return NULL;
  }

  XML_SetElementHandler(p, beginParseElement, endParseElement);
  XML_SetCharacterDataHandler(p, parseCharacterData);
  XML_SetCdataSectionHandler(p, parseCDataSectionStart, parseCDataSectionEnd);
  XML_SetCommentHandler(p, parseComment);

  if (! XML_Parse(p, Buff, (int)size, 1)) {
    cerr << "Parse error at line " <<
        XML_GetCurrentLineNumber(p) << ":" << endl <<
        XML_ErrorString(XML_GetErrorCode(p)) << endl;
    exit(-1);
  }

//  cout << "Finished parsing config file '" << szConfigFilename << "'" << endl;

  // cleanup
  delete[] Buff;
  XML_ParserFree(p);

  return pCurNode;
}

void serializeXMLNode(std::ostream& os, XMLNode* node) {
  if(node->tagName == CONFIG_EMPTY_STRING) {
    os << node->textContent;
    return;
  }
  os << "<" << node->tagName << " ";
  for(AttrIter it = node->attr.begin(); it != node->attr.end(); ++it) {
    os << it->first << "=\"" << it->second << "\" ";
  }
  if(node->children.size() == 0) { os << "/>"; }
  else {
    os << ">";
    for(NodeIter nit = node->children.begin(); nit != node->children.end(); ++nit) {
      serializeXMLNode(os, *nit);
    }
    os << "</" << node->tagName << ">";
  }
}

bool saveConfig(XMLNode* rootNode, const char* szConfigFilename) {
  if(rootNode->tagName == CONFIG_EMPTY_STRING) {
    cerr << "saveConfig() called with an XMLNode that does not have a tag name" << endl;
    return false;
  }

  ofstream file(szConfigFilename);
  bool bResult = false;

  if(file.is_open())
  {
    file << "<?xml version=\"1.0\" encoding=\"UTF-8\" ?>" << endl;
    serializeXMLNode(file, rootNode);
    file << endl;
    file.close();
    cout << "Config file '" << szConfigFilename << "' written to disk" << endl;
    bResult = true;
  }
  else {
    cerr << "Could not open config file '" << szConfigFilename << "'" << endl;
  }

  return bResult;
}

